package cn.iocoder.yudao.module.pay.refund.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.business.basic.dao.repository.SerialNoRedisRepository;
import cn.iocoder.yudao.framework.common.exception.Assertions;
import cn.iocoder.yudao.framework.common.exception.KnownServiceException;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.util.TransactionUtils;
import cn.iocoder.yudao.module.api.infrastructure.config.ConfigApiV2;
import cn.iocoder.yudao.module.api.infrastructure.config.dto.PayConfigRespDTO;
import cn.iocoder.yudao.module.api.pay.order.eums.UnifiedOrderStatusEnum;
import cn.iocoder.yudao.module.api.pay.order.eums.UnifiedPayChannelEnum;
import cn.iocoder.yudao.module.api.pay.refund.dto.PayRefundCreateDTO;
import cn.iocoder.yudao.module.api.pay.refund.dto.UnifiedRefundCreateDTO;
import cn.iocoder.yudao.module.api.pay.refund.enums.UnifiedRefundStatusEnum;
import cn.iocoder.yudao.module.api.pay.refund.vo.UnifiedRefundVO;
import cn.iocoder.yudao.module.pay.framework.PayClient;
import cn.iocoder.yudao.module.pay.framework.impl.cash.CashPayClient;
import cn.iocoder.yudao.module.pay.framework.impl.free.FreePayClient;
import cn.iocoder.yudao.module.pay.framework.impl.mallbook.MallBookWxLitePayClient;
import cn.iocoder.yudao.module.pay.framework.impl.mock.MockPayClient;
import cn.iocoder.yudao.module.pay.framework.impl.wallet.WalletPayClient;
import cn.iocoder.yudao.module.pay.order.dal.dataobject.PayOrderDO;
import cn.iocoder.yudao.module.pay.order.service.PayOrderService;
import cn.iocoder.yudao.module.pay.refund.convert.PayRefundConvert;
import cn.iocoder.yudao.module.pay.refund.dal.dataobject.PayRefundDO;
import cn.iocoder.yudao.module.pay.refund.dal.mysql.PayRefundMapper;
import cn.iocoder.yudao.module.pay.refund.mq.RefundOrderProducer;
import cn.iocoder.yudao.module.pay.refund.vo.PayRefundExportReqVO;
import cn.iocoder.yudao.module.pay.refund.vo.PayRefundPageReqVO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.api.infrastructure.ConfigConstants.CONFIG_PAY;
import static cn.iocoder.yudao.module.api.pay.ConfigConstants.PAY_REFUND_NO_PREFIX;
import static cn.iocoder.yudao.module.api.pay.ErrorCodeConstants.*;

/**
 * 退款订单 Service 实现类
 *
 * @author jason
 */
@Service
@Slf4j
@Validated
public class PayRefundServiceImpl implements PayRefundService {

    @Resource
    private PayRefundMapper payRefundMapper;

    @Resource
    private PayOrderService payOrderService;

    @Resource
    private RefundOrderProducer refundOrderProducer;
    @Resource
    private SerialNoRedisRepository orderSerialNoRedisRepository;

    @Resource
    private ConfigApiV2 configApiV2;

    private final Lock lock = new ReentrantLock();

    @Override
    public PayRefundDO getRefund(Long id) {
        return payRefundMapper.selectById(id);
    }

    @Override
    public PayRefundDO getRefundByNo(String no) {
        return payRefundMapper.selectByNo(no);
    }

    @Override
    public PageResult<PayRefundDO> getRefundPage(PayRefundPageReqVO pageReqVO) {
        return payRefundMapper.selectPage(pageReqVO);
    }

    @Override
    public List<PayRefundDO> getRefundList(PayRefundExportReqVO exportReqVO) {
        return payRefundMapper.selectList(exportReqVO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public PayRefundDO createRefund(PayRefundCreateDTO dto) {
        // 1.1 校验退款订单是否已经存在
        PayRefundDO refund = payRefundMapper.selectByPayNo(dto.getPayNo());
        if (refund != null) {
            // 是否有退款中的订单
            if (Objects.equals(UnifiedRefundStatusEnum.WAITING.getStatus(), refund.getStatus())) {
                doOrderRefund(refund);
                return refund;
            }
            throw new KnownServiceException("退款已存在");
        }
        // 1.2 校验付款单据
        PayOrderDO order = validatePayOrderCanRefund(dto);
        PayConfigRespDTO configRespDTO = configApiV2.getConfig(CONFIG_PAY, PayConfigRespDTO.class);
        String notifyUrl = configRespDTO.getRefundNotifyUrl() + "/" + order.getChannelCode();
        // 2.1 插入退款单
        refund = PayRefundConvert.INSTANCE.convert(dto);
        refund.setNo(orderSerialNoRedisRepository.generateNo(PAY_REFUND_NO_PREFIX));
        refund.setPayNo(order.getNo());
        refund.setChannelCode(order.getChannelCode());
        refund.setNotifyUrl(notifyUrl);
        refund.setChannelOrderNo(order.getChannelOrderNo());
        refund.setStatus(UnifiedRefundStatusEnum.WAITING.getStatus());
        refund.setPayPrice(order.getPrice());
        refund.setRefundPrice(dto.getRefundPrice());
        payRefundMapper.insert(refund);
        PayRefundDO finalRefund = refund;
        TransactionUtils.afterCommit(() -> {
            // 注意：事务提交后再发起第三方渠道退款，避免渠道退款报错时被回滚
            // 原因是：虽然调用支付渠道进行退款发生异常（网络请求超时），实际退款成功。
            // 这个结果，后续通过退款回调、或者退款轮询补偿可以拿到。
            // 最终，在异常的情况下，支付中心会异步回调业务的退款回调接口，提供退款结果
            doOrderRefund(finalRefund);
            return null;
        });
        // 返回退款编号
        return refund;
    }

    private void doOrderRefund(PayRefundDO refund) {
        UnifiedRefundCreateDTO unifiedRefundCreateDTO = buildUnifiedRefundCreateDTO(refund);
        switch (UnifiedPayChannelEnum.getByCode(refund.getChannelCode())) {
            case MOCK: {
                UnifiedRefundVO refundVO = SpringUtil.getBean(MockPayClient.class).unifiedRefund(unifiedRefundCreateDTO);
                refund.setStatus(refundVO.getStatus());
                refund.setChannelRefundNo(refundVO.getChannelRefundNo());
                refund.setChannelNotifyData(JSONUtil.toJsonStr(refundVO.getRawData()));
                payRefundMapper.updateById(refund);
                if (UnifiedRefundStatusEnum.isSuccess(refundVO.getStatus())) {
                    // 发送退款成功消息
                    refundOrderProducer.sendRefundSuccessMessage(refund.getNo());
                } else if (UnifiedRefundStatusEnum.isFailure(refund.getStatus())) {
                    // 发送退款失败消息
                    refundOrderProducer.sendRefundFailureMessage(refund.getNo());
                }
                break;
            }
            case FREE: {
                UnifiedRefundVO refundVO = SpringUtil.getBean(FreePayClient.class).unifiedRefund(unifiedRefundCreateDTO);
                refund.setStatus(refundVO.getStatus());
                refund.setChannelRefundNo(refundVO.getChannelRefundNo());
                refund.setChannelNotifyData(JSONUtil.toJsonStr(refundVO.getRawData()));
                payRefundMapper.updateById(refund);
                if (UnifiedRefundStatusEnum.isSuccess(refundVO.getStatus())) {
                    // 发送退款成功消息
                    refundOrderProducer.sendRefundSuccessMessage(refund.getNo());
                } else if (UnifiedRefundStatusEnum.isFailure(refund.getStatus())) {
                    // 发送退款失败消息
                    refundOrderProducer.sendRefundFailureMessage(refund.getNo());
                }
                break;
            }
            case CASH: {
                UnifiedRefundVO refundVO = SpringUtil.getBean(CashPayClient.class).unifiedRefund(unifiedRefundCreateDTO);
                refund.setStatus(refundVO.getStatus());
                refund.setChannelRefundNo(refundVO.getChannelRefundNo());
                refund.setChannelNotifyData(JSONUtil.toJsonStr(refundVO.getRawData()));
                payRefundMapper.updateById(refund);
                if (UnifiedRefundStatusEnum.isSuccess(refundVO.getStatus())) {
                    // 发送退款成功消息
                    refundOrderProducer.sendRefundSuccessMessage(refund.getNo());
                } else if (UnifiedRefundStatusEnum.isFailure(refund.getStatus())) {
                    // 发送退款失败消息
                    refundOrderProducer.sendRefundFailureMessage(refund.getNo());
                }
                break;
            }
            case WALLET: {
                UnifiedRefundVO refundVO = SpringUtil.getBean(WalletPayClient.class).unifiedRefund(unifiedRefundCreateDTO);
                refund.setStatus(refundVO.getStatus());
                refund.setChannelRefundNo(refundVO.getChannelRefundNo());
                refund.setChannelNotifyData(JSONUtil.toJsonStr(refundVO.getRawData()));
                payRefundMapper.updateById(refund);
                if (UnifiedRefundStatusEnum.isSuccess(refundVO.getStatus())) {
                    // 发送退款成功消息
                    refundOrderProducer.sendRefundSuccessMessage(refund.getNo());
                } else if (UnifiedRefundStatusEnum.isFailure(refund.getStatus())) {
                    // 发送退款失败消息
                    refundOrderProducer.sendRefundFailureMessage(refund.getNo());
                }
                break;
            }
            case WX_LITE:
            case MALLBOOK_WX_LITE: {
                wxLiteRefund(unifiedRefundCreateDTO, refund);
                break;
            }
            default:
                throw exception(PAY_ORDER_CHANNEL_NOT_SUPPORT);
        }
    }

    private UnifiedRefundCreateDTO buildUnifiedRefundCreateDTO(PayRefundDO refund) {
        UnifiedRefundCreateDTO unifiedRefundCreateDTO = new UnifiedRefundCreateDTO();
        unifiedRefundCreateDTO.setRefundNo(refund.getNo());
        unifiedRefundCreateDTO.setTradeNo(refund.getTradeNo());
        unifiedRefundCreateDTO.setPayNo(refund.getPayNo());
        unifiedRefundCreateDTO.setPayPrice(refund.getPayPrice());
        unifiedRefundCreateDTO.setRefundPrice(refund.getRefundPrice());
        unifiedRefundCreateDTO.setNotifyUrl(refund.getNotifyUrl());
        unifiedRefundCreateDTO.setReason(refund.getReason());
        unifiedRefundCreateDTO.setUserIp(refund.getUserIp());
        unifiedRefundCreateDTO.setUserUa(refund.getUserUa());
        return unifiedRefundCreateDTO;
    }

    private void wxLiteRefund(UnifiedRefundCreateDTO dto, PayRefundDO refund) {
        UnifiedRefundVO refundVO = SpringUtil.getBean(MallBookWxLitePayClient.class).unifiedRefund(dto);
        refund.setChannelRefundNo(refundVO.getChannelRefundNo());
        refund.setChannelErrorCode(refundVO.getChannelErrorCode());
        refund.setChannelErrorMsg(refundVO.getChannelErrorMsg());
        refund.setChannelNotifyData(JsonUtils.toJsonString(refundVO));
        payRefundMapper.updateById(refund);
        // 2.3 处理退款返回
        if (UnifiedRefundStatusEnum.isSuccess(refundVO.getStatus())) {
            // 发送退款成功消息
            refundOrderProducer.sendRefundSuccessMessage(refund.getNo());
        } else if (UnifiedRefundStatusEnum.isFailure(refund.getStatus())) {
            // 发送退款失败消息
            refundOrderProducer.sendRefundFailureMessage(refund.getNo());
        }
    }

    /**
     * 校验付款单据是否可以退款
     *
     * @param dto 退款申请信息
     * @return 付款单据
     */
    private PayOrderDO validatePayOrderCanRefund(PayRefundCreateDTO dto) {
        PayOrderDO order = payOrderService.getOrderByNo(dto.getPayNo());
        if (order == null) {
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        // 校验状态，必须是已支付、或者已退款
        if (!UnifiedOrderStatusEnum.isSuccessOrRefund(order.getStatus())) {
            throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
        }
        // 校验金额，退款金额不能大于原定的金额
        if (dto.getRefundPrice() + order.getRefundPrice() > order.getPrice()) {
            throw exception(REFUND_PRICE_EXCEED);
        }
        return order;
    }

    @Override
    public int syncRefund() {
        // 1. 查询指定创建时间内的待退款订单
        List<PayRefundDO> refunds = payRefundMapper.selectListByStatus(UnifiedRefundStatusEnum.WAITING.getStatus());
        if (CollUtil.isEmpty(refunds)) {
            return 0;
        }
        // 2. 遍历执行
        int count = 0;
        for (PayRefundDO refund : refunds) {
            count += syncRefund(refund) ? 1 : 0;
        }
        return count;
    }

    /**
     * 同步单个退款订单
     *
     * @param refund 退款订单
     * @return 是否同步到
     */
    private boolean syncRefund(PayRefundDO refund) {
        try {
            lock.lock();
            PayClient<?> payClient;
            if (UnifiedPayChannelEnum.isWxLite(refund.getChannelCode())) {
                //payClient = SpringUtil.getBean(WxLitePayClient.class);
                payClient = SpringUtil.getBean(MallBookWxLitePayClient.class);
            } else if (Objects.equals(UnifiedPayChannelEnum.WALLET.getCode(), refund.getChannelCode())) {
                payClient = SpringUtil.getBean(WalletPayClient.class);
            } else if (Objects.equals(UnifiedPayChannelEnum.MOCK.getCode(), refund.getChannelCode())) {
                payClient = SpringUtil.getBean(MockPayClient.class);
            } else if (Objects.equals(UnifiedPayChannelEnum.CASH.getCode(), refund.getChannelCode())) {
                payClient = SpringUtil.getBean(CashPayClient.class);
            } else if (Objects.equals(UnifiedPayChannelEnum.FREE.getCode(), refund.getChannelCode())) {
                payClient = SpringUtil.getBean(FreePayClient.class);
            } else {
                log.warn("[syncRefund] 不支持的渠道({})", refund.getChannelCode());
                return false;
            }
            UnifiedRefundVO refundVO = payClient.getRefund(refund.getPayNo(), refund.getNo());
            String rawData = refundVO.getRawData() == null ? "" : JsonUtils.toJsonString(refundVO.getRawData());
            LambdaUpdateWrapper<PayRefundDO> updateWrapper = new LambdaUpdateWrapper<PayRefundDO>()
                    .eq(PayRefundDO::getNo, refund.getNo());
            updateWrapper.set(PayRefundDO::getChannelNotifyData, rawData);
            updateWrapper.set(PayRefundDO::getChannelErrorCode, refundVO.getChannelErrorCode());
            updateWrapper.set(PayRefundDO::getChannelErrorMsg, refundVO.getChannelErrorMsg());
            if (StrUtil.isNotBlank(refundVO.getChannelRefundNo())) {
                updateWrapper.set(PayRefundDO::getChannelRefundNo, refundVO.getChannelRefundNo());
            }
            if (UnifiedRefundStatusEnum.isSuccess(refundVO.getStatus())) {
                updateWrapper.set(PayRefundDO::getStatus, UnifiedRefundStatusEnum.SUCCESS.getStatus());
                LocalDateTime successTime = refundVO.getSuccessTime();
                if (successTime == null) {
                    successTime = LocalDateTime.now();
                }
                updateWrapper.set(PayRefundDO::getSuccessTime, successTime);
            } else if (UnifiedRefundStatusEnum.isFailure(refundVO.getStatus())) {
                updateWrapper.set(PayRefundDO::getStatus, UnifiedRefundStatusEnum.FAILURE.getStatus());
            }
            boolean b = payRefundMapper.update(updateWrapper) > 0;
            log.info("[syncRefund][tradeNo({})refundNo({}) 的 rawData({}) status({}) 更新结果 {}]",
                    refund.getTradeNo(), refund.getNo(), rawData, refundVO.getStatus(), b);
            if (b) {
                // 此处需要读取最新的状态
                refund = payRefundMapper.selectByNo(refund.getNo());
                if (UnifiedRefundStatusEnum.isSuccess(refund.getStatus())) {
                    // 发送退款成功消息
                    refundOrderProducer.sendRefundSuccessMessage(refund.getNo());
                } else if (UnifiedRefundStatusEnum.isFailure(refund.getStatus())) {
                    // 发送退款失败消息
                    refundOrderProducer.sendRefundFailureMessage(refund.getNo());
                }
            }
            return b;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public String notify(String channelCode, Map<String, String> params, String body, Map<String, String> headers) {
        log.info("[refund/notify][channelCode({}) 收到回调数据] params: {}\nbody: {}\nheaders: {}", channelCode, params, body, headers);
        if (UnifiedPayChannelEnum.isWxLite(channelCode)) {
            // 解析通知数据
            //UnifiedRefundVO refundVO = SpringUtil.getBean(WxLitePayClient.class).parseRefundNotify(params, body, headers);
            UnifiedRefundVO refundVO = SpringUtil.getBean(MallBookWxLitePayClient.class).parseRefundNotify(params, body, headers);
            PayRefundDO refundDO = payRefundMapper.selectByNo(refundVO.getRefundNo());
            Assertions.notNull(refundDO, "退款单不存在：" + refundVO.getRefundNo());
            syncRefund(refundDO);
            return JsonUtils.toJsonString(refundVO);
        } else {
            throw new KnownServiceException(GlobalErrorCodeConstants.NOT_IMPLEMENTED.getCode(), "渠道回调未实现：" + channelCode);
        }
    }

}
